Los transformers son una arquitectura medianamente reciente que ha tenido espectaculares resultados en procesamiento de lenguaje natural. Su estructura está basada en capas de atención, que a su vez tienen varias "cabezas". A diferencia de como se usa en redes neuronales recurrentes, las atenciones en un transformer son la base principal de la representación final del input, además de poder ser entrenadas en paralelo, y por ende en mayores volúmenes de datos de modo más eficiente.

Para este proyecto hemos querido comparar resultados de estas arquitecturas con un clasificador visto en el curso que también es apropiado para la tarea de predicción, como lo es Naive-Bayes. Más aún, se usaran vectores que corresponden a tweets extraidos de estos modelos, y se analizarán usando herramientas de clustering revisadas en el curso. Para comprender las nociones del funcionamiento de la arquitectura y como modela los elementos linguisticos, observemos de forma resumida como funciona un módulo de atención:

Una manera de visualizar la arquitectura es pensar en una matriz, donde cada elemento codifica la importancia de un elemento de la sequencia de input con respecto a la secuencia de output. En este ejemplo mostramos una representación idealizada de un módulo de auto-atención (sefl attention), que es el modo en que los codificadores basados en transformers procesan las sequencias. Un poco más formalmente, consideramos matrices $W$ que serán aprendidas durante el entrenamientom y se relacionan del modo siguiente:

Si bien la capacidad de interpretación que tienen los modelos va bajando a medida que la magnitud de estos sube, es interesante tener acceso al interior de los modelos. Con la biblioteca bertviz podemos visualizar las capas de atención para cada cabeza y cada capa para un input dado.
from transformers import AutoTokenizer, AutoModelForSequenceClassification, utils
from bertviz import model_view, head_view
import csv
utils.logging.set_verbosity_error() # Suppress standard warnings
from config import rank_emojis_text, labels_es
Cargando modelo
MODEL = f"ccarvajal/beto-emoji"
folder = MODEL.replace('ccarvajal','modelos')
try:
tokenizer = AutoTokenizer.from_pretrained(folder)
model = AutoModelForSequenceClassification.from_pretrained(folder, output_attentions=True)
except ValueError:
tokenizer = AutoTokenizer.from_pretrained(MODEL)
tokenizer.save_pretrained(folder)
model = AutoModelForSequenceClassification.from_pretrained(MODEL, output_attentions=True)
model.save_pretrained(folder)
ejemplo = "Tapas + sangria = @ Ocaña Barcelona"
rank_emojis_text(ejemplo,model,tokenizer,labels_es)
1) 😍 0.418 2) 🇪🇸 0.2464 3) 👌 0.097 4) 😊 0.0895 5) ❤ 0.0742 6) 😁 0.0254 7) 💙 0.0105 8) 😎 0.0093 9) 💜 0.0064 10) 💕 0.0048 11) 😉 0.004 12) 😜 0.0032 13) ✨ 0.0026 14) 💞 0.0025 15) 💘 0.0018 16) 😘 0.0016 17) 😂 0.0013 18) 💪 0.0008 19) 🎶 0.0007
Este ejemplo es uno de muchos que no está bien clasificado. El emoji en cuestión usado en el tweet es 🇪🇸 y no 😍.
Podemos usar este ejemplo para visualizar la capa de atención.
def display_model_view(input_text):
inputs = tokenizer.encode(input_text, return_tensors='pt') # Tokenize input text
outputs = model(inputs) # Run model
attention = outputs[-1] # Retrieve attention from model outputs
tokens = tokenizer.convert_ids_to_tokens(inputs[0]) # Convert input ids to token strings
model_view(attention, tokens, include_layers=[11])
display_model_view(ejemplo)
def display_head_view(input_text):
inputs = tokenizer.encode(input_text, return_tensors='pt') # Tokenize input text
outputs = model(inputs) # Run model
attention = outputs[-1] # Retrieve attention from model outputs
tokens = tokenizer.convert_ids_to_tokens(inputs[0]) # Convert input ids to token strings
head_view(attention, tokens, layer=11)
display_head_view(ejemplo)
Un token que obviamos en el la representación idealizada es el token [CLS] (o < s > si la arquitectura de base es Roberta en vez de Bert). La representación de este token a través de la última capa es aquel que se usa como input para el clasificador, que es una capa feedforward con una función de activación. Al pasar el cursor por el token [CLS] a la izquierda podemos observar aquellos tokens que tienen más peso en el cómputo de aquella representación que se usará en la predicción.
En este caso, se observa una influencia más fuerte del token tapa, que es una comida típica de bar en Barcelona. Una hopótesis razonable es pensar que aquel token está "empujando" la clasificación hacia un emoji equivocado. Veamos lo que sucede cuando sólamente predecimos el emoji de la frase "tapas".
rank_emojis_text("tapas",model,tokenizer,labels_es)
1) 😍 0.6418 2) ❤ 0.1665 3) 😊 0.0505 4) 👌 0.0341 5) 🇪🇸 0.0297 6) 💙 0.0228 7) 💜 0.0148 8) 💕 0.0143 9) 💘 0.0063 10) 💞 0.0045 11) 😁 0.0043 12) 😎 0.0039 13) ✨ 0.0028 14) 😘 0.0008 15) 😉 0.0008 16) 😜 0.0007 17) 😂 0.0006 18) 🎶 0.0004 19) 💪 0.0004
Se observa que el emoji 😍 es aquel con más probabilidad con un 64% de probabilidad, contra apenas 2,9% del token 🇪🇸.
Veamos otro ejemplo.
otro_ejemplo = "Vamoooos!! #Gym #healthy @ Albacete Capital"
rank_emojis_text(otro_ejemplo,model,tokenizer,labels_es)
1) 💪 0.9921 2) 😁 0.0012 3) 😂 0.0011 4) 🇪🇸 0.0009 5) 👌 0.0007 6) 😜 0.0006 7) 😘 0.0006 8) 😉 0.0006 9) 😎 0.0004 10) 😊 0.0003 11) 💙 0.0003 12) 🎶 0.0003 13) 😍 0.0002 14) ❤ 0.0002 15) 💜 0.0001 16) ✨ 0.0001 17) 💘 0.0001 18) 💕 0.0001 19) 💞 0.0001
Acá la clasificación es correcta pues predice 💪 con alta probabilidad, que es efectivamente el label asociado. Observemos las capas de atención.
display_model_view(otro_ejemplo)
display_head_view(otro_ejemplo)
Aquellos tokens con más influencia con respecto a [CLS] son gym, healthy y @. As interesante señalar que estas palabras son prestadas del inglés, y por ende sus tokens aparecen "cortados", puesto que el tokenizer está diseñado para español. Si hacemos una "españolización" de este mismo tweet obtenemos lo siguiente.
otro_ejemplo = "Vamos!! #Gimnasio #saludable @ Albacete Capital"
rank_emojis_text(otro_ejemplo,model,tokenizer,labels_es)
1) 💪 0.9857 2) 😁 0.0023 3) 🇪🇸 0.0023 4) 😊 0.002 5) 😘 0.0015 6) 😉 0.0011 7) 👌 0.001 8) 😜 0.0009 9) 😎 0.0005 10) 💙 0.0005 11) 😂 0.0004 12) 🎶 0.0004 13) ✨ 0.0003 14) ❤ 0.0003 15) 💜 0.0002 16) 😍 0.0002 17) 💘 0.0002 18) 💕 0.0002 19) 💞 0.0002
display_head_view(otro_ejemplo)
La predicción sigue siendo la misma, además de que haya más influencia del token vamos que de @ en esta visualización.
Lo interesante de esto es observar como la arquitectura toma tokens y modela la relación entre ellos de modo que la comprensión que tiene de la frase sea consistente con su significado. Tener tokens cortados que provenian de otro idioma y tener vamos escrito de una manera más coloquial no fue impedimento para que la clasificación diera con el emoji correcto por un amplio margen.